security: close 6 CRITICAL findings from 2026-04-08 audit#232
Merged
Conversation
The accepted JWT algorithm list was sourced from settings.jwt_algorithm, meaning an operator (or attacker with env-write access) setting JWT_ALGORITHM=none could silently disable signature verification. Hardcodes HS256 in create_access_token and decode_token. Adds 5 TDD guards in tests/test_auth_jwt_algorithm.py. See docs/security/audit-2026-04-08.md CRIT-1.
…RIT-4) Before this fix, any authenticated DJ could pair or reassign a kiosk to an event owned by another DJ by supplying the victim's 6-char event code. Both complete_kiosk_pairing and assign_kiosk fetched the event by code and proceeded without checking event.created_by_user_id. Extracts a helper _assert_caller_owns_event that allows admin bypass and raises 403 for DJs attempting to act on events they do not own. Called after the 'Event not found' check in both handlers. New TestKioskIdor class in test_kiosk_api.py adds 4 guards: - DJ cannot pair to victim's event (kiosk stays in 'pairing' state) - DJ cannot reassign their own kiosk to victim's event - Admin CAN pair to any event - Admin CAN reassign any kiosk to any event All 17 existing test_kiosk_api.py tests and 52 test_kiosk.py tests continue to pass. See docs/security/audit-2026-04-08.md CRIT-3, CRIT-4.
Before this fix, GET /api/public/events/{code}/stream had no rate limit,
no auth, and no event-existence check. Unauthenticated attackers could:
(a) Open unlimited long-lived SSE connections (DoS via FD exhaustion)
(b) Brute-force 6-char event codes to passively eavesdrop on real-time
request, now-playing, and bridge-status events
Now validates event exists and is not archived/expired before subscribing,
and applies @limiter.limit('10/minute') at the connection-open boundary.
New test_sse_security.py adds 4 guards:
- 404 for unknown event code
- 404/410 for archived event
- 404/410 for expired event
- 429 after exceeding 10 connections/minute
All 120 SSE-related tests pass (existing test_events.py, test_event_bus.py,
test_bridge_commands.py all green).
See docs/security/audit-2026-04-08.md CRIT-5.
Adds token_version column to User (migration 033) and 'tv' claim to every JWT. On decode, tv must be present and match user.token_version; otherwise 401. POST /api/auth/logout bumps the counter. See docs/security/audit-2026-04-08.md CRIT-2.
Third-party actions were pinned to mutable tags (e.g. @v6, @v2). A compromised tag could exfiltrate GITHUB_TOKEN, WINGET_PAT, and inject malicious code into the bridge-app installer binaries. Pinned actions: - codecov/codecov-action@v6 -> @57e3a136b779b5... (v6) - softprops/action-gh-release@v2 -> @153bb8e0440... (v2) First-party actions (actions/*, github/*) are exempt — they are maintained by GitHub and present lower supply-chain risk. New test_workflow_pins.py guardrail runs on every CI run and fails if any third-party action uses a non-SHA ref. See docs/security/audit-2026-04-08.md CRIT-6.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes all 6 CRITICAL vulnerabilities identified in the full-stack security audit from 2026-04-08.
JWT_ALGORITHM=nonecould silently disable signature verification. Fixed: hardcodedalgorithms=["HS256"]jti, no version). Fixed:token_versioncolumn on User,tvclaim in JWT,POST /api/auth/logoutendpoint@limiter.limit("10/minute")+ event existence/expiry/archived validationcodecov/codecov-actionandsoftprops/action-gh-release, plus a guardrail testTDD methodology
Every fix followed strict RED-GREEN-REFACTOR:
22 new tests across 4 new files + 1 extended file. 1 existing test updated:
test_auth_jwt_algorithm.py::test_decode_accepts_valid_hs256needed"tv": 0added to test data after CRIT-2 landed (thedecode_tokenfunction now rejects tokens missing thetvclaim).Files changed
Production (10 files):
server/app/services/auth.py— hardcoded_JWT_ALGORITHM,tvclaim extractionserver/app/api/deps.py—token_versioncomparison inget_current_userserver/app/api/auth.py—tvin login token, new/logoutendpointserver/app/api/kiosk.py—_assert_caller_owns_eventhelper, called in both handlersserver/app/api/sse.py— rate limit + existence check + expiry/archived validationserver/app/models/user.py—token_versioncolumnserver/app/schemas/auth.py—token_versioninTokenDataserver/alembic/versions/033_add_user_token_version.py— migration.github/workflows/ci.yml— SHA-pinned codecov action.github/workflows/release.yml— SHA-pinned softprops actionTests (5 files):
server/tests/test_auth_jwt_algorithm.py— 5 tests (CRIT-1)server/tests/test_auth_jwt_revocation.py— 5 tests (CRIT-2)server/tests/test_kiosk_api.py— 4 new IDOR tests (CRIT-3+4)server/tests/test_sse_security.py— 4 tests (CRIT-5)server/tests/test_workflow_pins.py— 4 parametrized tests (CRIT-6)Test plan